package cn.xfdtool.slowsql.listener.impl;

import cn.hutool.core.collection.CollectionUtil;
import cn.hutool.core.date.DateUtil;
import cn.hutool.core.util.ArrayUtil;
import cn.hutool.core.util.ObjectUtil;
import cn.xfdtool.slowsql.dto.SlowSQLDTO;
import cn.xfdtool.slowsql.dto.SlowSQLNotifyDTO;
import cn.xfdtool.slowsql.listener.SlowSQLParserListener;
import com.alibaba.fastjson.JSON;
import lombok.extern.slf4j.Slf4j;
import org.apache.ibatis.mapping.BoundSql;
import org.apache.ibatis.mapping.MappedStatement;
import org.apache.ibatis.mapping.ParameterMapping;
import org.apache.ibatis.mapping.ParameterMode;
import org.apache.ibatis.plugin.Invocation;
import org.apache.ibatis.reflection.MetaObject;
import org.apache.ibatis.session.Configuration;
import org.apache.ibatis.type.JdbcType;
import org.apache.ibatis.type.TypeException;
import org.apache.ibatis.type.TypeHandler;
import org.apache.ibatis.type.TypeHandlerRegistry;

import java.sql.SQLException;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Date;
import java.util.List;

/**
 * 抽象慢SQL解析器
 *
 * @author xfd
 * 2022/5/14
 */
@Slf4j
public abstract class AbstractSlowSQLParserListener implements SlowSQLParserListener {

    @Override
    public SlowSQLNotifyDTO parse(SlowSQLDTO slowSQLDTO) {
        try {
            return doParse(slowSQLDTO);
        } catch (Throwable e) {
            log.error("parse slowsql error.", e);
            final Object[] args = slowSQLDTO.getInvocation().getArgs();
            final MappedStatement ms = (MappedStatement)args[0];
            final Object param = args[1];
            final BoundSql boundSql = ms.getBoundSql(param);
            final String sql = boundSql.getSql();
            final Object paramObject = boundSql.getParameterObject();
            return SlowSQLNotifyDTO.builder()
                    .parseSuccess(Boolean.FALSE)
                    .parseError(e)
                    .methodName(ms.getId())
                    .args(paramObject)
                    .prepareSQL(sql)
                    .throwable(slowSQLDTO.getThrowable())
                    .resultRowCount(parseResultRowCount(slowSQLDTO.getResult()))
                    .extProps(slowSQLDTO.getExtProps())
                    .build();
        }
    }

    /**
     * 解析结果条数
     * @param result
     * @return
     */
    protected int parseResultRowCount(Object result) {
        if (ObjectUtil.isNull(result)) {
            return -1;
        } else if (result instanceof Collection) {
            Collection coll = (Collection)result;
            return coll.size();
        }
        return -1;
    }

    /**
     * 替换预编译参数
     *
     * @param prepareSQL
     * @param invocation
     * @return
     */
    protected String replacePrepareParam(String prepareSQL, Invocation invocation) {
        final Object[] args = invocation.getArgs();
        final MappedStatement ms = (MappedStatement) args[0];
        final Object parameterObject = args[1];
        final BoundSql boundSql = ms.getBoundSql(parameterObject);
        final String sql = boundSql.getSql();
        List<ParameterMapping> parameterMappings = boundSql.getParameterMappings();
        if (ObjectUtil.isEmpty(parameterMappings)) {
            return prepareSQL;
        }
        List<Object> params = new ArrayList<Object>();
        TypeHandlerRegistry typeHandlerRegistry = ms.getConfiguration().getTypeHandlerRegistry();
        Configuration configuration = ms.getConfiguration();
        for (int i = 0; i < parameterMappings.size(); i++) {
            ParameterMapping parameterMapping = parameterMappings.get(i);
            if (parameterMapping.getMode() != ParameterMode.OUT) {
                Object value = null;
                String propertyName = parameterMapping.getProperty();
                if (boundSql.hasAdditionalParameter(propertyName)) { // issue #448 ask first for additional params
                    value = boundSql.getAdditionalParameter(propertyName);
                } else if (parameterObject == null) {
                    value = null;
                } else if (typeHandlerRegistry.hasTypeHandler(parameterObject.getClass())) {
                    value = parameterObject;
                } else {
                    MetaObject metaObject = configuration.newMetaObject(parameterObject);
                    value = metaObject.getValue(propertyName);
                }
                TypeHandler typeHandler = parameterMapping.getTypeHandler();
                JdbcType jdbcType = parameterMapping.getJdbcType();
                if (value == null && jdbcType == null) {
                    jdbcType = configuration.getJdbcTypeForNull();
                }
                params.add(value);
            }
        }
        StringBuilder source = new StringBuilder(prepareSQL);
        StringBuilder result = new StringBuilder();
        int sourceIndex = 0;
        for (int i = 0; i < source.length(); i++) {
            char ch = source.charAt(i);
            if (ch == '?') {
                result.append(convertParam(params.get(sourceIndex++)));
            } else if (ch == '\r' || ch == '\n') {
                result.append(" ");
            } else {
                result.append(ch);
            }
        }
        return result.toString();
    }

    /**
     * 转换参数
     * @param sourceParam
     * @return
     */
    protected String convertParam(Object sourceParam) {
        if (sourceParam == null) {
            return "'null'";
        } else if (CharSequence.class.isAssignableFrom(sourceParam.getClass())) {
            return "'" + sourceParam.toString() + "'";
        } else if (Number.class.isAssignableFrom(sourceParam.getClass())) {
            return sourceParam.toString();
        } else if (Collection.class.isAssignableFrom(sourceParam.getClass())
                || ArrayUtil.isArray(sourceParam)) {
            String arrJson = JSON.toJSONString(sourceParam);
            return arrJson.substring(1, arrJson.length() - 1);
        } else if (Enum.class.isAssignableFrom(sourceParam.getClass())) {
            Enum e = (Enum)sourceParam;
            return String.valueOf(e.ordinal());
        } else if (sourceParam instanceof Date) {
            return "'" + DateUtil.formatDateTime((Date)sourceParam) + "'";
        } else if (sourceParam instanceof Boolean) {
            Boolean bl = (Boolean)sourceParam;
            return bl ? "1" : "0";
        }
        return "''";
    }

    /**
     * 执行数据解析
     *
     * @param slowSQLDTO
     * @return
     * @throws Throwable
     */
    protected abstract SlowSQLNotifyDTO doParse(SlowSQLDTO slowSQLDTO) throws Throwable;
}
